package com.nulldev.util.concurrency.threadIt.v4.executors.bqde;

import java.util.Collections;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.AbstractExecutorService;
import java.util.concurrent.Callable;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.RunnableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

import com.nulldev.util.JVM.Arguments;
import com.nulldev.util.VariableAPI.ArrayUtils;
import com.nulldev.util.VariableAPI.MathUtil;
import com.nulldev.util.VariableAPI.util.random.GlobalRandom;
import com.nulldev.util.concurrency.threadIt.v4.emapi.ExecutionPreference;
import com.nulldev.util.concurrency.threadIt.v4.emapi.ExecutorManager;
import com.nulldev.util.concurrency.threadIt.v4.emapi.compat.ExecutableFuture;
import com.nulldev.util.concurrency.threadIt.v4.emapi.compat.ExecutableRunnableFuture;
import com.nulldev.util.concurrency.threadIt.v4.emapi.extensions.ExecutorFeatureExtensions;
import com.nulldev.util.concurrency.threadIt.v4.emapi.extensions.ExtendedExecutor;
import com.nulldev.util.concurrency.threadIt.v4.emapi.extensions.IExecutorUnwinder;
import com.nulldev.util.concurrency.threadIt.v4.emapi.extensions.IFutureTaskFlags;
import com.nulldev.util.concurrency.threadIt.v4.emapi.uft.Executable;
import com.nulldev.util.concurrency.threadIt.v4.utils.impls.TEConditions;
import com.nulldev.util.data.Variables;
import com.nulldev.util.data.Arrays.arrays.FastArrayList;
import com.nulldev.util.data.Arrays.arrays.FastLists;
import com.nulldev.util.manage.Version;

/**
 * BQDE (Batch Queued Dynamic Executor)
 * 
 * @author null
 * @since 3.0
 * @api-version 4
 */
public class BQDE extends AbstractExecutorService implements ExtendedExecutor {

	/**
	 * The Batch Queued Dynamic Executor.
	 * 
	 * Prior to 1.1: <br>
	 * BQDE uses the following model: <br>
	 * [ BQDEThread (25%) ] [ Batch Executor (75%) ] [ LP (25%) ] <br>
	 * Batches are made by the executing thread by collecting candidates. <br>
	 * 
	 * Post 1.1: <br>
	 * [ BQDEThread (100%) ] [ LP (25%) ] <br>
	 * Batches have been replaced with DCTE's submission chains. <br>
	 * These are generated at submission, low-priority and preference submission are
	 * excluded.
	 */
	static interface BQDEQueueFlags {
		public static final int BQDE_QUEUE_PREFER_DEDICATED = 2;
		public static final int BQDE_QUEUE_PREFER_LOW_PRIORITY = 3;
		public static final int BQDE_QUEUE_ASYNC_TASK = 4;
		public static final int BQDE_QUEUE_PRIOR_SUBMISSION_FAILED = 5;
		public static final int BQDE_QUEUE_PREVENT_BATCHING = 6;
	}

	protected static final int BQDE_CONTEXT_TYPE_UNKNOWN = 0;
	protected static final int BQDE_CONTEXT_TYPE_DEDICATED = 1;
	protected static final int BQDE_CONTEXT_TYPE_DEDICATED_LP = 2;

	public static int getOptimalBatchingSize() {
		try {
			final int cpus = Runtime.getRuntime().availableProcessors();
			// XXX: Should octa-core chips have batch size of 8 or the old 6?
			if (cpus >= 8)
				return 8;
			if (cpus >= 4)
				return 6;
			if (cpus >= 2)
				return 4;
			return 2;
		} catch (Throwable tx) {
			return 4;
		}
	}

	protected static final int BQDE_MAXIMUM_FULL_CHAIN_SIZE = getOptimalBatchingSize();

	protected final static boolean NEW_METHOD_ENFORCE_PRIORITIES = false;

	protected final static boolean ENABLE_THREAD_YIELDING = true;

	private final static boolean ENABLE_NATIVE_BATCHING = Arguments.notArguments("--BQDE.disableNativeBatching") || ExecutorManager.ENABLE_BATCH_OPERATIONS;
	/* Object[] -> {Runnable, Integer} or {BQDETaskChain, Integer} */
	private final LinkedTransferQueue<Object[]> tasks = new LinkedTransferQueue<Object[]>();
	private final List<ControllableThread> threads = new CopyOnWriteArrayList<ControllableThread>();
	private final List<ControllableThread> threads_lp = new CopyOnWriteArrayList<ControllableThread>();
	private final int threadLimit, batchExecutionSize;
	private final AtomicReference<IExecutorUnwinder> unwinder = new AtomicReference<IExecutorUnwinder>();

	public BQDE() {
		this(32, getOptimalBatchingSize());
	}

	/**
	 * The changelog has been moved into the CHANGELOG file.
	 */
	public static final Version VERSION = new Version(1, 1, 0);

	public static final int MINIMUM_THREADS_FOR_LOW_PRIORITY = 20;

	private static final boolean DISABLE_FORKED_FEATURE_SET = Arguments.hasArgument("--BQDE.disableFFS");
	private final boolean enableLPT;

	public BQDE(final int threadLimit, final int batchSize) {
		this(threadLimit, batchSize, false);
	}

	public BQDE(final int threadLimit, final int batchSize, final boolean enableLPT) {
		if (threadLimit <= 0)
			throw new IllegalArgumentException("threadLimit should be more than zero!");
		if (batchSize < 0)
			throw new IllegalArgumentException("batchSize should be more than (or equal to) zero!");
		this.threadLimit = threadLimit;
		final boolean allowLPT = threadLimit > MINIMUM_THREADS_FOR_LOW_PRIORITY;
		this.enableLPT = enableLPT && allowLPT;
		if (!allowLPT) {
			synchronized (System.out) {
				System.out.println("[!] [BQDE] LPT has been disabled due to low thread count.");
			}
		}
		this.batchExecutionSize = batchSize;
		// FIXME: Shouldn't we be doing a 80%:20% ratio for threads?
		for (int i = 0; i < threadLimit; i++) {
			threads.add(new BQDEThread(this, i).autoStart());
		}
		Collections.shuffle(this.threads, GlobalRandom.GLOBAL_FALLBACK_RANDOM);
		if (enableLPT && allowLPT) {
			final int lowPriThreads = MathUtil.clamp(4, 32, threadLimit / 4);
			for (int i = 0; i < lowPriThreads; i++) {
				threads_lp.add(new BQDEThreadLP(this, i).autoStart());
			}
			Collections.shuffle(this.threads_lp, GlobalRandom.GLOBAL_FALLBACK_RANDOM);
		}
	}

	private int threadCount() {
		int threads = 0;
		for (final ControllableThread t : this.threads) {
			if (t instanceof BQDEThread) {
				threads++;
			}
		}
		for (final ControllableThread t : this.threads_lp) {
			if (t instanceof BQDEThreadLP)
				threads++;
		}
		return threads;
	}

	private int activeThreadCount(final boolean lptOnly) {
		int threads = 0;
		if (!lptOnly) {
			for (final ControllableThread t : this.threads) {
				if (t instanceof BQDEThread) {
					if (((BQDEThread) t).isBusy())
						threads++;
				}
			}
		}
		for (final ControllableThread t : this.threads_lp) {
			if (t instanceof BQDEThreadLP)
				if (((BQDEThreadLP) t).isBusy())
					threads++;
		}
		return threads;
	}

	protected final AtomicBoolean STOP = new AtomicBoolean();

	private boolean splitChainAndSubmit(final Runnable[] r) {
		if (r.length > BQDE_MAXIMUM_FULL_CHAIN_SIZE) {
			final int amountOfChains = (int) Math.ceil(r.length / BQDE_MAXIMUM_FULL_CHAIN_SIZE);
			final Runnable[][] splitUp = ArrayUtils.splitArray(r, amountOfChains);
			for (final Runnable[] chain : splitUp) {
				final BQDETaskChain tc = new BQDETaskChain(chain);
				try {
					if (!this.tasks.tryTransfer(new Object[]
						{ tc, 0 }, 1000, TimeUnit.MILLISECONDS))
						return false;
				} catch (InterruptedException ex) {
					return false;
				}
			}
			return true;
		} else {
			final BQDETaskChain tc = new BQDETaskChain(r);
			try {
				return this.tasks.tryTransfer(new Object[]
					{ tc, 0 }, 1000, TimeUnit.MILLISECONDS);
			} catch (InterruptedException ex) {
				return false;
			}
		}
	}

	private boolean submitNoChaining(final List<Runnable> r) {
		for (final Runnable task : r) {
			if (!transferOrXfer(make_task(task, BQDEQueueFlags.BQDE_QUEUE_ASYNC_TASK)))
				return false;
		}

		return true;
	}

	private boolean splitChainAndSubmit(final List<Runnable> r) {
		if (r.size() > BQDE_MAXIMUM_FULL_CHAIN_SIZE) {
			final int amountOfChains = (int) Math.ceil(r.size() / BQDE_MAXIMUM_FULL_CHAIN_SIZE);
			final Runnable[][] splitUp = ArrayUtils.splitArray(r.toArray(new Runnable[r.size()]), amountOfChains);
			for (final Runnable[] chain : splitUp) {
				final BQDETaskChain tc = new BQDETaskChain(chain);
				try {
					if (!this.tasks.tryTransfer(new Object[]
						{ tc, 0 }, 1000, TimeUnit.MILLISECONDS))
						return false;
				} catch (InterruptedException ex) {
					return false;
				}
			}
			return true;
		} else {
			final BQDETaskChain tc = new BQDETaskChain(r.toArray(new Runnable[r.size()]));
			try {
				return this.tasks.tryTransfer(new Object[]
					{ tc, 0 }, 1000, TimeUnit.MILLISECONDS);
			} catch (InterruptedException ex) {
				return false;
			}
		}
	}

	private boolean transferOrXfer(final Object[] r) {
		try {
			return this.tasks.tryTransfer(r, 1000, TimeUnit.MILLISECONDS);
		} catch (InterruptedException ex) {
			__signal_rejected_execution(r);
			return false;
		}
	}

	private void __signal_rejected_execution(final Runnable r) {
		if (r == null)
			return;

		if (!(r instanceof Executable)) {
			throw new RuntimeException("Submission rejected!");
		} else {
			((Executable<?>) r).rejectSubmission();
		}
	}

	private void __signal_rejected_execution(final Object[] r) {
		if (r == null || r.length != 2)
			return;

		if (r[0] instanceof Executable) {
			((Executable<?>) r[0]).rejectSubmission();
		} else if (r[0] instanceof List<?>) {
			((List<?>) r[0]).forEach(candidate -> {
				if (candidate instanceof Executable) {
					((Executable<?>) candidate).rejectSubmission();
				}
			});
		} else {
			/* NO-OP */
		}
	}

	private boolean isValidType(final int contextType, final int flag) {
		switch (contextType) {
			case BQDE_CONTEXT_TYPE_DEDICATED:
				return flag == BQDEQueueFlags.BQDE_QUEUE_PREFER_DEDICATED;
			case BQDE_CONTEXT_TYPE_DEDICATED_LP:
				return flag == BQDEQueueFlags.BQDE_QUEUE_PREFER_LOW_PRIORITY;
			case BQDE_CONTEXT_TYPE_UNKNOWN:
			default:
				return true;
		}
	}

	protected Object takeTask(final int contextType) throws InterruptedException {
		final Object[] peeked = this.tasks.poll(10, TimeUnit.SECONDS);
		if (peeked == null || peeked.length != 2)
			return null;
		if (peeked[0] instanceof BQDETaskChain) {
			return peeked[0];
		} else if (NEW_METHOD_ENFORCE_PRIORITIES) {
			final boolean isValidTask = isValidType(contextType, (int) peeked[1]);
			if (isValidTask) {
				return (Runnable) peeked[0];
			} else {
				this.tasks.put(peeked);
				return null;
			}
		} else {
			return (Runnable) peeked[0];
		}
	}

	@Override
	public boolean executorSupportsSlotCount() {
		return true;
	}

	private static final ExecutorFeatureExtensions[] SUPPORTED = new ExecutorFeatureExtensions[]
		{ ExecutorFeatureExtensions.EEX_core_executor_slots, ExecutorFeatureExtensions.EEX_query_slots_supported,
				ExecutorFeatureExtensions.EEX_core_submit_all_tasks, ExecutorFeatureExtensions.EEX_core_drain_task_queue,
				ExecutorFeatureExtensions.EEX_core_execute_with_preference, ExecutorFeatureExtensions.EEX_query_slots_supported,
				ExecutorFeatureExtensions.EEX_query_batching_supported, ExecutorFeatureExtensions.EEX_extended_unwinder_debugging,
				ExecutorFeatureExtensions.EEX_query_thread_affinity_supported, ExecutorFeatureExtensions.EEX_executor_low_priority_threads,
				ExecutorFeatureExtensions.EEX_executor_attemptable_submissions, ExecutorFeatureExtensions.EEX_executor_attemptable_batch_submissions,
				ExecutorFeatureExtensions.EEX_executor_attemptable_drain_submissions, ExecutorFeatureExtensions.EEX_query_executor_forking,
				ExecutorFeatureExtensions.EEX_executor_sched_accepts_always, ExecutorFeatureExtensions.EEX_compat_native_concurrency_api,
				ExecutorFeatureExtensions.EEX_executor_attemptable_submissions2, ExecutorFeatureExtensions.EEX_query_debug,
				ExecutorFeatureExtensions.EEX_query_executor_large_batches,

				(ENABLE_NATIVE_BATCHING ? ExecutorFeatureExtensions.EEX_executor_native_batching : ExecutorFeatureExtensions.BLANK) };

	@Override
	public ExecutorFeatureExtensions[] supported() {
		return SUPPORTED;
	}

	@Override
	public boolean executorSupportsBatching() {
		return ENABLE_NATIVE_BATCHING && !DISABLE_FORKED_FEATURE_SET;
	}

	protected Object[] make_task(final Runnable r) {
		return make_task(r, 0);
	}

	protected Object[] make_task(final Runnable r, final int flags) {
		TEConditions.validRunnable(r);
		return new Object[]
			{ r, flags };
	}

	@Override
	public void batch(final Runnable command) {
		if (command == null)
			return;
		if (this.unwinder.get() != null)
			this.unwinder.get().newTask(command);
		try {
			if (!transferOrXfer(make_task(command, BQDEQueueFlags.BQDE_QUEUE_ASYNC_TASK))) {
				__signal_rejected_execution(command);
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	@Override
	public void batchAll(final Runnable[] r) {
		if (r == null || r.length == 0)
			return;
		if (this.unwinder.get() != null)
			this.unwinder.get().newTasks(r);

		if (!splitChainAndSubmit(r))
			throw new RuntimeException("batchAll: splitChainAndSubmit() failed!!!");
	}

	@Override
	public boolean supportsFeature(final ExecutorFeatureExtensions feature) {
		return ArrayUtils.contains(SUPPORTED, feature);
	}

	@Override
	public int executor_getFreeSlots() {
		return MathUtil.clamp(1, this.threadLimit, this.threadCount() - this.activeThreadCount(false));
	}

	@Override
	public void shutdown() {
		this.STOP.lazySet(true);
		ControllableThread e;
		try {
			while ((e = (ControllableThread) this.threads.remove(0)) != null) {
				e.stopThread();
			}
		} catch (IndexOutOfBoundsException f) {
		}
	}

	@Override
	public List<Runnable> shutdownNow() {
		final List<Object[]> r0 = new FastArrayList<Object[]>(this.tasks, true);
		this.shutdown();
		final List<Runnable> r = new FastArrayList<Runnable>(r0.size(), true);
		r0.forEach((i) -> {
			r.add((Runnable) i[0]);
		});
		return r;
	}

	@Override
	public boolean isShutdown() {
		return this.STOP.get();
	}

	@Override
	public boolean isTerminated() {
		return this.STOP.get();
	}

	@Override
	public boolean awaitTermination(final long timeout, final TimeUnit unit) throws InterruptedException {
		this.shutdown();
		return true;
	}

	@Override
	public void execute(final Runnable command) {
		if (command == null)
			return;
		if (this.unwinder.get() != null)
			this.unwinder.get().newTask(command);
		try {
			if (!(command instanceof Executable)) {
				if (!transferOrXfer(make_task(command))) {
					throw new RuntimeException("execute: Submission rejected!");
				}
			} else {
				final boolean shouldBatch = (int) ((Executable<?>) command).getFlag(IFutureTaskFlags.FT_PREFER_BATCHING) == IFutureTaskFlags.FT_TRUE;
				if (!transferOrXfer(make_task(command, shouldBatch ? 0 : BQDEQueueFlags.BQDE_QUEUE_ASYNC_TASK))) {
					__signal_rejected_execution(command);
				}
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public int getThreadLimit() {
		return this.threadLimit;
	}

	@Override
	public void execute(final Runnable command, final ExecutionPreference e) {
		if (command == null)
			return;
		if (this.unwinder.get() != null)
			this.unwinder.get().newTask(command);
		try {
			final int flag;
			switch (e) {
				case DEDICATED_THREAD:
					flag = BQDEQueueFlags.BQDE_QUEUE_PREFER_DEDICATED;
					break;
				case FORK_POOLS:
					flag = 0;
					break;
				default:
					flag = BQDEQueueFlags.BQDE_QUEUE_ASYNC_TASK;
					break;
			}
			if (!transferOrXfer(make_task(command, flag))) {
				__signal_rejected_execution(command);
			}
		} catch (Exception e1) {
			throw new RuntimeException(e1);
		}
	}

	@Override
	public void executor_submitAll(final List<Runnable> r) {
		if (r == null || r.isEmpty())
			return;
		if (this.unwinder.get() != null)
			this.unwinder.get().newTasks(r);

		final boolean defer_batching = false; /* TODO: EMAPIUtils.shouldDeferOptimizations(r); */

		if (defer_batching ? submitNoChaining(r) : !splitChainAndSubmit(r))
			throw new RuntimeException("executor_submitAll: Submission rejected!");
	}

	@Override
	public void executor_drainTasks(final Queue<Runnable> async_queue, final int batchSize) {
		if (async_queue == null || async_queue.isEmpty())
			return;
		if (batchSize <= 0)
			return;
		if (this.unwinder.get() != null)
			this.unwinder.get().newTasks(async_queue, batchSize);
		final Runnable[] rq = new Runnable[batchSize];
		for (int i = 0; i < batchSize; i++) {
			Runnable r = null;
			try {
				r = async_queue.poll();
			} catch (Throwable t) {
				t.printStackTrace();
			} finally {
				if (r == null)
					continue;
				rq[i] = r;
			}
		}

		if (!splitChainAndSubmit(rq)) {
			throw new RuntimeException("executor_drainTasks: Failed to execute splitChainAndSubmit.");
		}
	}

	@Override
	public void hookUnwinder(final IExecutorUnwinder unwinder) {
		if (unwinder != null)
			this.unwinder.set(unwinder);
	}

	@Override
	public String toString() {
		return "BQDE[hash=" + MathUtil.toHex(this.hashCode()) + ",threads=" + this.threadCount() + ",batchSize=" + this.batchExecutionSize + ",forking="
				+ !DISABLE_FORKED_FEATURE_SET + "]";
	}

	/* EEX_query_thread_affinity_supported */

	@Override
	public boolean executorSupportsThreadAffinity() {
		return false;
	}

	@Override
	public boolean executorSupportsThreadPinning() {
		return false;
	}

	/* EEX_executor_low_priority_threads */

	@Override
	public boolean supportsLPT() {
		return true;
	}

	@Override
	public boolean isLPTActive() {
		return this.enableLPT;
	}

	@Override
	public void setLPTActive(final boolean state) {
		this.unsupported(ExecutorFeatureExtensions.EEX_executor_low_priority_threads);
	}

	@Override
	public int[] getLPTs() {
		if (!this.enableLPT || this.threads_lp.isEmpty())
			return new int[0];
		final int baseLine = this.threads.size();
		final int[] nx = new int[this.threads_lp.size()];
		for (int i = 0; i < this.threads_lp.size(); i++)
			nx[i] = (baseLine + i);
		return nx;
	}

	@Override
	public void submitLP(final Runnable command) {
		if (!transferOrXfer(make_task(command, BQDEQueueFlags.BQDE_QUEUE_PREFER_LOW_PRIORITY)))
			__signal_rejected_execution(command);
	}

	@Override
	public void submitLP(final Executable<?> r) {
		TEConditions.validExecutable(r);
		if (r instanceof Runnable) {
			this.submitLP((Runnable) r);
		} else {
			this.submitLP(() -> {
				r.run_st();
			});
		}
	}

	/* EEX_executor_attemptable_submissions */

	@Override
	public boolean supportsAttemptableSubmissions() {
		return true;
	}

	@Override
	public boolean attempt_submit(final Runnable command) {
		if (command == null)
			return false;
		if (this.unwinder.get() != null)
			this.unwinder.get().newTask(command);
		try {
			return transferOrXfer(make_task(command, BQDEQueueFlags.BQDE_QUEUE_ASYNC_TASK));
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	/* EEX_executor_attemptable_submissions2 */

	@Override
	public boolean supportsAttemptableSubmissions2() {
		return true;
	}

	@Override
	public <T> boolean attempt_submit2(final Executable<T> task) {
		if (this.transferOrXfer(make_task((Runnable) task, BQDEQueueFlags.BQDE_QUEUE_ASYNC_TASK))) {
			task.signalQueued();
			return true;
		} else {
			task.rejectSubmission();
			return false;
		}
	}

	/* EEX_executor_attemptable_batch_submissions */

	@Override
	public boolean supportsAttemptableBatchSubmissions() {
		return ENABLE_NATIVE_BATCHING;
	}

	@Override
	public boolean attempt_batch(final Runnable command) {
		if (command == null)
			return false;
		if (this.unwinder.get() != null)
			this.unwinder.get().newTask(command);
		try {
			return transferOrXfer(make_task(command, BQDEQueueFlags.BQDE_QUEUE_ASYNC_TASK));
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	/* EEX_executor_attemptable_drain_submissions */

	@Override
	public boolean supportsAttemptableDrainSubmissions() {
		return true;
	}

	@Override
	public int attempt_drain(final Queue<Runnable> async_queue, final int batchSize) {
		if (async_queue == null || async_queue.isEmpty())
			return 0;
		if (batchSize <= 0)
			return 0;
		if (this.unwinder.get() != null)
			this.unwinder.get().newTasks(async_queue, batchSize);
		final Runnable[] rq = new Runnable[batchSize];
		for (int i = 0; i < batchSize; i++) {
			Runnable r = null;
			try {
				r = async_queue.poll();
			} catch (Throwable t) {
				t.printStackTrace();
			} finally {
				if (r == null)
					continue;
				rq[i] = r;
			}
		}
		if (!splitChainAndSubmit(rq)) {
			return 0;
		} else {
			return rq.length;
		}
	}

	/* EEX_query_executor_forking */

	@Override
	public boolean executorIsForking() {
		return ExecutorManager.ENABLE_BATCH_OPERATIONS;
	}

	/* EEX_compat_native_concurrency_api */

	@Override
	public Future<?> submit(final Runnable task) {
		TEConditions.validRunnable(task);
		final ExecutableRunnableFuture<Object> ef = new ExecutableRunnableFuture<Object>(task, null);
		execute(ef);
		ef.signalQueued();
		return ef;
	}

	@Override
	public <T> Future<T> submit(final Runnable task, final T result) {
		TEConditions.validRunnable(task);
		final ExecutableRunnableFuture<T> ef = new ExecutableRunnableFuture<T>(task, result);
		execute(ef);
		ef.signalQueued();
		return ef;
	}

	@Override
	public <T> Future<T> submit(final Callable<T> task) {
		final ExecutableFuture<T> ef = new ExecutableFuture<T>(task);
		execute(ef);
		ef.signalQueued();
		return ef;
	}

	@Override
	protected <T> RunnableFuture<T> newTaskFor(final Callable<T> callable) {
		return new ExecutableFuture<T>(callable);
	}

	@Override
	protected <T> RunnableFuture<T> newTaskFor(final Runnable runnable, final T value) {
		TEConditions.validRunnable(runnable);
		return new ExecutableRunnableFuture<T>(runnable, value);
	}

	/* EEX_query_debug */

	@Override
	public Object debug_query(final EEXDebugQueryType type) {
		switch (Variables.requireNonNullObject(type, "type")) {
			case EEX_DEBUG_TOTAL_THREAD_COUNT:
				return this.threadCount();
			case EEX_DEBUG_ACTIVE_THREAD_COUNT:
				return this.activeThreadCount(false);
			/* LPT */
			case EEX_DEBUG_LPT_ACTIVE:
				return this.enableLPT;
			case EEX_DEBUG_LPT_ACTIVE_THREAD_COUNT:
				return this.activeThreadCount(true);
			case EEX_DEBUG_LPT_TOTAL_THREAD_COUNT:
				return this.threads_lp.size();
			/* Queueing */
			case EEX_DEBUG_TASKS_IN_QUEUE:
				return this.tasks.size();
			/* */
			case EEX_DEBUG_ACTIVE_TASKS_STACK_TRACE: {
				final List<StackTraceElement[]> o = FastLists.list();
				for (final ControllableThread cts : this.threads) {
					if (!cts.isBusy())
						continue;
					o.add(cts.getThread().getStackTrace());
				}
				for (final ControllableThread cts : this.threads_lp) {
					if (!cts.isBusy())
						continue;
					o.add(cts.getThread().getStackTrace());
				}
				return o.toArray(new StackTraceElement[o.size()][]);
			}
			/* */
			case EEX_DCTE_NON_STANDARD_BATCHING_CHAIN_SIZE:
				return BQDE_MAXIMUM_FULL_CHAIN_SIZE;
			/* */
			default:
				return null;
		}
	}

	/* EEX_query_executor_large_batches */

	@Override
	public boolean executorAllowLargeBatches() {
		return true;
	}

	@Override
	public int executorPreferredBatchSize() {
		return BQDE_MAXIMUM_FULL_CHAIN_SIZE;
	}

}
